﻿using System;
using System.Linq;
using System.Reflection;
using System.Globalization;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace NazTek.Core
{
    /// <summary>
    /// ORM representation of a database table.
    /// </summary>
    public interface IEntity
    {
        /// <summary>
        /// Gets/sets primary key value.
        /// </summary>
        string Id { get; set; }
        /// <summary>
        /// Indexer.  Gets/sets field value via reflection.
        /// </summary>
        /// <param name="fieldName">Name of the field whose value to get or set.</param>
        /// <returns>Field value.</returns>
        string this[string fieldName] { get; set; }
        /// <summary>
        /// Gets primary key field name.
        /// </summary>
        string PrimaryKeyFieldName { get; }
        /// <summary>
        /// Gets database table name.
        /// </summary>
        string DatabaseTableName { get; }
    }
    /// <summary>
    /// Default <see cref="IEntity"/> implementation.
    /// </summary>
    public class DefaultEntity : IEntity
    {
        /// <summary>
        /// Gets/sets primary key value.
        /// </summary>
        public virtual string Id { get; set; }
        /// <summary>
        /// Indexer.  Gets/sets field value via reflection.
        /// </summary>
        /// <param name="fieldName">Name of the field whose value to get or set.</param>
        /// <returns>Field value.</returns>
        public string this[string fieldName]
        {
            get
            {
                var data = GetProperty(fieldName).GetValue(this, null);
                return data == null ? null : data.ToString();
            }
            set
            {
                GetProperty(fieldName).SetValue(this, value);
            }
        }
        /// <summary>
        /// Gets the primary key member name.
        /// </summary>
        /// <returns>
        /// Entity's primary key name, from <see cref="System.ComponentModel.DataAnnotations.KeyAttribute"/> attribute.
        /// </returns>
        /// <remarks>The method assumes there's one primary key member.</remarks>
        public string PrimaryKeyFieldName
        {
            get
            {
                var prop = GetProperty(typeof (KeyAttribute));
                if (prop != null)
                    return prop.Name;

                var fieldName = "id";
                TextInfo textInfo = new CultureInfo("en-US", false).TextInfo;

                prop = GetProperty(fieldName);
                if (prop != null)
                    return prop.Name;
                prop = GetProperty(textInfo.ToUpper(fieldName));
                if (prop != null)
                    return prop.Name;
                prop = GetProperty(textInfo.ToTitleCase(fieldName));
                if (prop != null)
                    return prop.Name;
                prop = GetProperty(string.Format("{0}{1}", GetType().Name, fieldName));
                if (prop != null)
                    return prop.Name;
                prop = GetProperty(string.Format("{0}{1}", GetType().Name, textInfo.ToUpper(fieldName)));
                if (prop != null)
                    return prop.Name;
                prop = GetProperty(string.Format("{0}{1}", GetType().Name, textInfo.ToTitleCase(fieldName)));
                return prop != null ? prop.Name : "key not found";
            }
        }
        /// <summary>
        /// Gets the database table name from entity attribute.
        /// </summary>
        public string DatabaseTableName
        {
            get
            {
                var tableAttribute = GetType().GetCustomAttribute<TableAttribute>();
                return tableAttribute != null ? tableAttribute.Name : GetType().Name;
            }
        }
        /// <summary>
        /// Gets the first matching property of given attribute, via reflection.
        /// </summary>
        /// <param name="customAttributeType">Attribute to filter properties by.</param>
        /// <returns>First property of matching attribute.</returns>
        private PropertyInfo GetProperty(Type customAttributeType)
        {
            var entityType = GetType();
            var allPropInfo = entityType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
            var attributeProperty = allPropInfo.FirstOrDefault(pi => pi.GetCustomAttributes(customAttributeType, false).Length == 1);
            return attributeProperty;
        }
        /// <summary>
        /// Gets property by name, via reflection.
        /// </summary>
        /// <param name="memberName">Property name.</param>
        /// <returns>Named property.</returns>
        private PropertyInfo GetProperty(string memberName)
        {
            var entityType = GetType();
            var propInfo = entityType.GetProperty(memberName, BindingFlags.Public | BindingFlags.Instance);
            return propInfo;
        }
    }
}